#pragma region Licensing
/*
       LOGL::GLib -> LorgonJortle's OpenGL Game Library
       Copyright (C) 2011  Jesse 'LorgonJortle' Wilkerson

       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma endregion

#include "Texture2.h"

using namespace LOGL_GLib::Utilities;

namespace LOGL_GLib
{
     namespace Content
     {

          /******************************************************************************
           Texture2::Texture2();
                    Arguments:
                         void

                    Description:
                         Constructs a Texture2 object.

                    Returns:
                         The Texture2 object.
          *******************************************************************************/
          Texture2::Texture2()
          {
               ID = 0;
               Width = 0;
               Height = 0;
               Depth = 0;
               InternalFormat = 0;
               Format = 0;
               Size = 0;
               Data = 0;
               IsAllocated = GL_FALSE;
          }

          /******************************************************************************
           Texture2::~Texture2();
                    Arguments:
                         void

                    Description:
                         Destructs the Texture2 and releases any indebted resources.

                    Returns:
                         void
          *******************************************************************************/
          Texture2::~Texture2()
          {
               /* Get rid of any resources we have borrowed. */
               Destroy();
          }

          /******************************************************************************
           GLboolean Texture2::Load(const std::string& _Filename);
                    Arguments:
                         const std::string& _Filename:
                              The location of the file to load as a texture.

                    Description:
                         Loads texture data from the specified file.

                    Returns:
                         void
          *******************************************************************************/
          GLboolean Texture2::Load(const std::string& _Filename)
          {
               try
               {
                    /* Destroy any existing data. */
                    Destroy();

                    /* Find what the file type is. */
                    if(_Filename.find(".tga") != std::string::npos)
                         LoadTGA(_Filename);

                    /* Allocate a spot for the texture in the OpenGL server. */
                    glGenTextures(1, &ID);
                    /* Bind the texture so that we can set some properties. */
                    glBindTexture(GL_TEXTURE_2D, ID);

                    /* Clamp the texture to the border by default. */
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

                    /* Use a linear filter by default. */
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

                    /* Load the texel data into the texture object. */
                    glPixelStorei(GL_UNPACK_ALIGNMENT, GL_TRUE);
                    glTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Width, Height, 0, Format, GL_UNSIGNED_BYTE, Data);
               }
               catch(...)
               {
                    /* Make sure that we release any data that may have been allocated before the error. */
                         if(IsAllocated)
                              Destroy();

                         return GL_FALSE;
               }

               return GL_TRUE;
          }

          /******************************************************************************
           GLboolean Texture2::Load(const SDL_Surface* const _Surface);
                    Arguments:
                         const SDL_Surface* const _Surface:
                              The surface to extract texture data from.

                    Description:
                         Loads texture data from the specified surface.

                    Returns:
                         void
          *******************************************************************************/
          GLboolean Texture2::Load(const SDL_Surface* const _Surface)
          {
               /* Check if it's a valid pointer. */
               if(_Surface == 0)
                    return GL_FALSE;

               /* Destroy any existing data. */
               Destroy();

               /* Determine the format of the pixels. */
               switch (_Surface->format->BytesPerPixel)
               {
                    case 4:
                    {
                         if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
                              Format = GL_BGRA;
                         else
                              Format = GL_RGBA;
                    } break;

                    case 3:
                    {
                         if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
                              Format = GL_BGR;
                         else
                              Format = GL_RGB;
                    } break;

                    default:
                    {
                         return GL_FALSE;
                    } break;
               }

               /* Assign the texture's dimensions. */
               Width = _Surface->w;
               Height = _Surface->h;

               /* Allocate space for the texture. */
               glGenTextures(1, &ID);
               /* Bind the texture so we can upload its data. */
               glBindTexture(GL_TEXTURE_2D, ID);

               /* Set some parameters that change how the texture it drawn. */
               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

               /* Upload the data from the surface to the texture. */
               glTexImage2D(GL_TEXTURE_2D, 0, _Surface->format->BytesPerPixel, Width, Height, 0, Format, GL_UNSIGNED_BYTE, _Surface->pixels);

               return GL_TRUE;
          }

          /******************************************************************************
           GLvoid Texture2::LoadTGA(const std::string& _Filename);
                    Arguments:
                         const std::string& _Filename:
                              The location of the file to load as a texture.

                    Description:
                         Loads texture data from the specified TGA file.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid Texture2::LoadTGA(const std::string& _Filename)
          {
               /* The header of the image we're reading. */
               TGAHeader header;
               /* The stream that the file will be read with. */
               std::ifstream reader;

               /* Clear the memory of the header to zeros. */
               memset(&header, 0, sizeof(TGAHeader));

               /* Open the file, and exit if it fails. */
               reader.open(_Filename.c_str(), std::ios::binary);
               if(!reader.is_open())
                    throw Exception("Failed to open texture file: " + _Filename);

               /* Read the header of the TGA file. */
               reader.read((GLchar*)&header.IDLength, 1);
               reader.read((GLchar*)&header.ColorMapType, 1);
               reader.read((GLchar*)&header.ImageType, 1);
               reader.read((GLchar*)&header.ColorMapStart, 2);
               reader.read((GLchar*)&header.ColorMapLength, 2);
               reader.read((GLchar*)&header.ColorMapBits, 1);
               reader.read((GLchar*)&header.XStart, 2);
               reader.read((GLchar*)&header.YStart, 2);
               reader.read((GLchar*)&header.Width, 2);
               reader.read((GLchar*)&header.Height, 2);
               reader.read((GLchar*)&header.BitsPerPixel, 1);
               reader.read((GLchar*)&header.Descriptor, 1);

               /* If we're on an Apple system, we need to do a byte swap. */
#ifdef __APPLE__
               LITTLE_ENDIAN_WORD(&header.ColorMapStart);
               LITTLE_ENDIAN_WORD(&header.ColorMapLength);
               LITTLE_ENDIAN_WORD(&header.XStart);
               LITTLE_ENDIAN_WORD(&header.YStart);
               LITTLE_ENDIAN_WORD(&header.Width);
               LITTLE_ENDIAN_WORD(&header.Height);
#endif

               /* Perform checks on the integrity of the header's data. */
               if(header.BitsPerPixel != 8 && header.BitsPerPixel != 24 && header.BitsPerPixel != 32)
                    throw Exception("The texture provided has an invalid format: Bits Per Pixels: " + _Filename);
               if(header.Width <= 0 || header.Height <= 0)
                    throw Exception("The texture provided has an invalid format: Width/Height = 0: " + _Filename);

               /* Copy the header data into the texture. */
               Width = header.Width;
               Height = header.Height;
               Depth = header.BitsPerPixel / 8;
               Size = header.Width * header.Height * header.BitsPerPixel;

               /* Find the format of the image. */
               switch(Depth)
               {
               case 1:
                    {
                         Format = GL_LUMINANCE;
                         InternalFormat = GL_LUMINANCE;
                    } break;
               case 3:
                    {
                         Format = GL_BGR;
                         InternalFormat = GL_RGB;
                    } break;
               case 4:
                    {
                         Format = GL_BGRA;
                         InternalFormat = GL_RGBA;
                    } break;
               default:
                    {
                         /* Didn't find an appropriate format. */
                         throw Exception("The texture provided has an unrecognizable format: " + _Filename);
                    } break;
               }

               /* Allocate the texel array. */
               Data = new GLbyte[Size];
               IsAllocated = GL_TRUE;

               /* Read in the rest of the image data. */
               reader.read((GLchar*)Data, Size);

               /* Close our file stream. */
               reader.close();
          }

          /******************************************************************************
           Texture2::Bind(GLenum _Unit) const;
                    Arguments:
                         GLenum _Unit:
                              The unit to bind the texture to.

                    Description:
                         Binds this texture to the current state.

                         Returns:
                         void
          *******************************************************************************/
          GLvoid Texture2::Bind(GLenum _Unit) const
          {
               glActiveTexture(_Unit);
               glBindTexture(GL_TEXTURE_2D, ID);
          }

          /******************************************************************************
           Texture2::Bind() const;
                    Arguments:
                         void

                    Description:
                         Unbinds this texture from the current state.

                         Returns:
                         void
          *******************************************************************************/
          GLvoid Texture2::Unbind() const
          {
               glBindTexture(GL_TEXTURE_2D, 0);
          }

          /******************************************************************************
           GLvoid Texture2::Destroy();
                    Arguments:
                         void

                    Description:
                         Release any indebted resources the Texture2 has.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid Texture2::Destroy()
          {
               /* If data has been allocated, go ahead and release it. */
               if(IsAllocated)
                    if(Data)
                    {
                         delete[] Data;
                         Data = 0;
                    }

               IsAllocated = GL_FALSE;

               Unbind();

               /* Now remove the texture object from the OpenGL server if it has been created. */
               if(glIsTexture(ID))
                    glDeleteTextures(1, &ID);
          }

     } /* Namespace Content. */
} /* Namespace LOGL_GLib. */
